struct LearnerInput
{
    short *data;
    int length;
};

struct LearnerExample
{
    //
    // X
    //
    LearnerInput input;

    //
    // Y (must be 0 or 1)
    //
    int output;
};

struct LearnerDataset
{
    void PartitionDataset(LearnerDataset &A, LearnerDataset &B, double AToBRatio) const
    {
        A.examples.clear();
        B.examples.clear();
        for(UINT exampleIndex = 0; exampleIndex < examples.size(); exampleIndex++)
        {
            if(rnd() < AToBRatio)
            {
                A.examples.push_back(examples[exampleIndex]);
            }
            else
            {
                B.examples.push_back(examples[exampleIndex]);
            }
        }
    }
    void WriteDataset(ostream &os) const
    {
        for(int inputIndex = 0; inputIndex < examples[0].input.length; inputIndex++)
        {
            os << "v" << inputIndex << '\t';
        }
        os << endl;
        for(UINT exampleIndex = 0; exampleIndex < examples.size(); exampleIndex++)
        {
            const LearnerExample &curExample = examples[exampleIndex];
            for(int inputIndex = 0; inputIndex < curExample.input.length; inputIndex++)
            {
                os << curExample.input.data[inputIndex] << '\t';
            }
            os << endl;
        }
    }

    vector<LearnerExample> examples;
};

struct ClassificationErrorDetails
{
    int truePositives;
    int trueNegatives;
    int falsePositives;
    int falseNegatives;
    double precision;
    double recall;
    double errorRate;
};

class BinaryClassifier
{
public:
    virtual void Train(const LearnerDataset &dataset) = 0;
    virtual int Evaluate(const LearnerInput &input) const = 0;
    virtual double EvaluateProbability(const LearnerInput &input) const = 0;

    int ClassificationError(const LearnerExample &example) const
    {
        int result = Evaluate(example.input);
        if(result == example.output)
        {
            return 0;
        }
        else
        {
            return 1;
        }
    }

    double DatasetClassificationError(const LearnerDataset &examples) const
    {
        double errorSum = 0.0;
        for(UINT exampleIndex = 0; exampleIndex < examples.examples.size(); exampleIndex++)
        {
            const LearnerExample &curExample = examples.examples[exampleIndex];
            errorSum += ClassificationError(curExample);
        }
        return errorSum / examples.examples.size();
    }

    ClassificationErrorDetails DatasetClassificationErrorDetails(const LearnerDataset &examples) const
    {
        ClassificationErrorDetails details = {}; // Zero out the struct.

        for (UINT exampleIndex = 0; exampleIndex < examples.examples.size(); exampleIndex++)
        {
            int result = Evaluate(examples.examples[exampleIndex].input);
            int correctResult = examples.examples[exampleIndex].output;
            if (result && correctResult) {
                details.truePositives++;
            } else if (result && !correctResult) {
                details.falsePositives++;
            } else if (!result && correctResult) {
                details.falseNegatives++;
            } else if (!result && !correctResult) {
                details.trueNegatives++;
            }

            details.errorRate = details.falseNegatives + details.falsePositives;
            details.errorRate /= examples.examples.size();
            details.precision = (details.truePositives + 0.0) / (details.truePositives + details.falsePositives);
            details.recall = (details.truePositives + 0.0) / (details.truePositives + details.falseNegatives);
        }

        return details;
    }

    double DescribeDatasetClassificationError(const LearnerDataset &examples, bool outputInputs, ostream &os) const
    {
        double errorSum = 0.0;

        if(outputInputs)
        {
            for(int inputIndex = 0; inputIndex < examples.examples[0].input.length; inputIndex++)
            {
                os << "v" << inputIndex << '\t';
            }
        }

        os << "Real Class\tPredicted Class\tConfidence" << endl;
        for(UINT exampleIndex = 0; exampleIndex < examples.examples.size(); exampleIndex++)
        {
            const LearnerExample &curExample = examples.examples[exampleIndex];

            double confidence = EvaluateProbability(curExample.input);
            
            int classification = 1;
            if(confidence >= 0.5) classification = 0;

            if(classification != 0)
            {
                confidence = 1.0 - confidence;
            }

            for(int inputIndex = 0; inputIndex < curExample.input.length; inputIndex++)
            {
                os << curExample.input.data[inputIndex] << '\t';
            }
            os << curExample.output << '\t' << classification << '\t' << confidence << endl;

            errorSum += fabs(double(classification) - double(curExample.output));
        }
        os << "Average classification error: " << errorSum / examples.examples.size() << endl;
        return errorSum / examples.examples.size();
    }

private:

};